package com.cchc.k8s.apis;

import com.alibaba.fastjson.JSONObject;
import com.cchc.k8s.config.K8sException;
import com.cchc.k8s.kernel.DeploymentBuilder;
import com.cchc.k8s.request.CreateDeploymentRequest;
import com.cchc.k8s.request.RollOutDeploymentRequest;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1DeploymentList;
import io.kubernetes.client.openapi.models.V1Status;
import io.kubernetes.client.util.PatchUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Objects;

/**
 * file：DeploymentApi.java
 * description:Deployment操作api
 * date: 2021-09-17 16:21
 * author: wenrui
 * version: 1.0
 */
@Slf4j
@RestController
@RequestMapping("/dp")
public class DeploymentApi {

    @Autowired
    ApiClient apiClient;
    @Autowired
    DeploymentBuilder deploymentBuilder;
    @Value("${harbor.host}")
    String harborHost;
    @Value("${harbor.repository}")
    String harborRepository;

    static String scaleStr = "[{\"op\":\"replace\",\"path\":\"/spec/replicas\",\"value\":${scale}}]";

    static String rollOutStr = "{\"spec\": {\"template\": {\"spec\": {\"containers\": [{\"name\": \"${appName}\",\"image\":\"${imageAddr}\"}]}}}}";

    static String restartStr = "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"${appName}\",\"image\":\"${imageAddr}\",\"env\":[{\"name\":\"RESTART_DATE\",\"value\":\"${date}\"}]}]}}}}";

    /**
     * 创建对应命名空间下的Deployment
     *
     * @param nsName  命名空间名称
     * @param request 创建Deployment请求
     * @return V1Deployment
     */
    @PostMapping("/create/{nsName}")
    public V1Deployment createDeployment(@PathVariable String nsName, @RequestBody CreateDeploymentRequest request) {
        log.info("创建Deployment，请求参数为：{}", JSONObject.toJSONString(request));
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1Deployment nsDeployment;
        try {
            V1Deployment deployment = deploymentBuilder.build(request);
            log.info("构建Deployment对象完成：{}", JSONObject.toJSONString(deployment.toString()));
            nsDeployment = appsV1Api.createNamespacedDeployment(nsName, deployment, "true", null, null);
        } catch (ApiException e) {
            e.printStackTrace();
            log.error("创建命名空间：{}下的Deployment 失败：{}", nsName, e.getResponseBody());
            throw new K8sException("D" + e.getCode(), (String) JSONObject.parseObject(e.getResponseBody()).get("message"));
        }
        log.info("创建命名空间：{}下的Deployment成功，信息为：{}", nsName, nsDeployment.toString());
        return nsDeployment;
    }

    /**
     * @param nsName 命名空间名称
     * @param dpName Deployment名称
     * @return 删除结果
     */
    @DeleteMapping("/delete/{nsName}/{dpName}")
    public V1Status deleteDeployment(@PathVariable String nsName, @PathVariable String dpName) {
        V1Status v1Status;
        try {
            AppsV1Api appsV1Api = new AppsV1Api(apiClient);
            v1Status = appsV1Api.deleteNamespacedDeployment(dpName + "-dm", nsName, "true", null, 0, null, null, null);
        } catch (ApiException e) {
            e.printStackTrace();
            log.info("删除命名空间：{}下的Deployment：{}失败，信息：{}", nsName, dpName, e.getResponseBody());
            throw new K8sException("D" + e.getCode(), (String) JSONObject.parseObject(e.getResponseBody()).get("message"));
        }

        if (v1Status != null && Objects.equals(v1Status.getStatus(), "Success")) {
            log.info("删除命名空间：{}下的Deployment：{}成功，信息：{}", nsName, dpName, JSONObject.toJSONString(v1Status));
        }

        return v1Status;
    }

    /**
     * 查询Deployment 详情
     *
     * @param nsName 命名空间名称
     * @param dpName Deployment名称
     * @return String
     * @throws ApiException
     */
    @GetMapping("/detail/{nsName}/{dpName}")
    public V1Deployment detailDeployment(@PathVariable String nsName, @PathVariable String dpName) throws ApiException {
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1Deployment v1Deployment = appsV1Api.readNamespacedDeployment(dpName + "-dm", nsName, "true", null, null);
        log.info("查询命名空间：{}下的Deployment：{}详情，返回信息为：{}", nsName, dpName, v1Deployment.toString());
        return v1Deployment;
    }

    /**
     * 查询Deployment列表
     *
     * @param nsName 命名空间名称
     * @return String
     */
    @GetMapping("/list/{nsName}")
    public V1DeploymentList listDeployment(@PathVariable String nsName) {
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1DeploymentList v1DeploymentList;
        try {
            v1DeploymentList = appsV1Api.listNamespacedDeployment(nsName, "true", null, null, null, null, null, null, null, null, null);
        } catch (ApiException e) {
            e.printStackTrace();
            log.error("获取命名空间：{}下的Deployment列表失败，信息：{}", nsName, e.getResponseBody());
            throw new K8sException("D" + e.getCode(), (String) JSONObject.parseObject(e.getResponseBody()).get("message"));
        }
        log.info("获取命名空间：{}下的Deployment列表,响应信息为：{}", nsName, v1DeploymentList.toString());
        return v1DeploymentList;
    }

    /**
     * 弹性伸缩 scale
     *
     * @param nsName   命名空间
     * @param dpName   deployment 名称
     * @param replicas 副本数量
     * @return String
     */
    @GetMapping("/scale/{nsName}/{dpName}")
    public V1Deployment scaleDeployment(@PathVariable String nsName, @PathVariable String dpName, Integer replicas) {
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1Deployment deployment;
        String newJsonPatchStr = scaleStr.replace("${scale}", String.valueOf(replicas));
        log.info("弹性伸缩：命名空间：{}下的Deployment：{},请求信息为：{}", nsName, dpName, newJsonPatchStr);
        try {
            deployment =
                    PatchUtils.patch(
                            V1Deployment.class,
                            () -> appsV1Api.patchNamespacedDeploymentCall(
                                    dpName + "-dm",
                                    nsName,
                                    new V1Patch(newJsonPatchStr),
                                    "true",
                                    null,
                                    null,
                                    null,
                                    null),
                            V1Patch.PATCH_FORMAT_JSON_PATCH,
                            appsV1Api.getApiClient());
        } catch (ApiException e) {
            e.printStackTrace();
            log.error("弹性伸缩：命名空间：{}下的Deployment：{},异常信息为：{}", nsName, dpName, e.getResponseBody());
            throw new K8sException("S" + e.getCode(), (String) JSONObject.parseObject(e.getResponseBody()).get("message"));
        }
        log.info("弹性伸缩：命名空间：{}下的Deployment：{}成功，响应信息为：{}", nsName, dpName, deployment.toString());
        return deployment;
    }

    /**
     * 滚动升级
     *
     * @param nsName  命名空间
     * @param request deploy信息
     * @return 升级后的deploy
     */
    @PostMapping("/image/{nsName}")
    public V1Deployment imageDeployment(@PathVariable String nsName, @RequestBody RollOutDeploymentRequest request) {
        String imageAddr = harborHost + "/" + harborRepository + "/" + request.getAppName() + ":" + request.getAppImageVersion();
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1Deployment deployment;
        String newJsonPatchStr = rollOutStr.replace("${imageAddr}", imageAddr).replace("${appName}", request.getAppName());
        log.info("滚动升级：命名空间：{}下的Deployment：{},请求信息为：{}", nsName, JSONObject.toJSONString(request), newJsonPatchStr);
        try {
            deployment =
                    PatchUtils.patch(
                            V1Deployment.class,
                            () -> appsV1Api.patchNamespacedDeploymentCall(
                                    request.getAppName() + "-dm",
                                    nsName,
                                    new V1Patch(newJsonPatchStr),
                                    "true",
                                    null,
                                    null,
                                    null,
                                    null),
                            V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH,
                            appsV1Api.getApiClient());
        } catch (ApiException e) {
            e.printStackTrace();
            log.error("滚动升级：命名空间：{}下的Deployment：{},异常信息为：{}", nsName, request.getAppName(), e.getResponseBody());
            throw new K8sException("S" + e.getCode(), (String) JSONObject.parseObject(e.getResponseBody()).get("message"));
        }
        log.info("滚动升级：命名空间：{}下的Deployment：{}成功，响应信息为：{}", nsName, request.getAppName(), deployment.toString());
        return deployment;
    }

    /**
     * 重启
     *
     * @param nsName  命名空间
     * @param request 镜像信息
     * @return V1Deployment
     */
    @GetMapping("/restart/{nsName}")
    public V1Deployment restartDeployment(@PathVariable String nsName, @RequestBody RollOutDeploymentRequest request) {
        // 基本思路就是给Container添加一个无关紧要的环境变量，这个环境变量的值就是时间戳，
        // 而这个时间戳则是每次执行上述命令的系统当前时间。这样一来对于K8S来讲这个Deployment spec就变化了，
        // 就可以像Updating a deployment一样，重启Pod了。
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1Deployment deployment;
        String imageAddr = harborHost + "/" + harborRepository + "/" + request.getAppName() + ":" + request.getAppImageVersion();
        String newRestartStr = restartStr.replace("${imageAddr}", imageAddr).replace("${appName}", request.getAppName()).replace("${date}", LocalDateTime.now().toString());
        log.info("重启：命名空间：{}下的Deployment：{},请求信息为：{}", nsName, request.getAppName(), newRestartStr);
        try {
            deployment =
                    PatchUtils.patch(
                            V1Deployment.class,
                            () -> appsV1Api.patchNamespacedDeploymentCall(
                                    request.getAppName() + "-dm",
                                    nsName,
                                    new V1Patch(newRestartStr),
                                    "true",
                                    null,
                                    null,
                                    null,
                                    null),
                            V1Patch.PATCH_FORMAT_JSON_MERGE_PATCH,
                            appsV1Api.getApiClient());
        } catch (ApiException e) {
            e.printStackTrace();
            log.error("重启：命名空间：{}下的Deployment：{},异常信息为：{}", nsName, request.getAppName(), e.getResponseBody());
            throw new K8sException("S" + e.getCode(), (String) JSONObject.parseObject(e.getResponseBody()).get("message"));
        }

        log.info("重启：命名空间：{}下的Deployment：{}成功，响应信息为：{}", nsName, request.getAppName(), deployment.toString());

        return deployment;
    }
}
